package com.aps.server.service;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.aps.algorithm.entity.BasicEquipSchedulingResult;
import com.aps.algorithm.heuristic.genetic.dto.ApsOperationDto;
import com.aps.algorithm.heuristic.genetic.model.Chromosome;
import com.aps.algorithm.mapper.BasicEquipSchedulingResultMapper;
import com.aps.common.code.DistributedSnowflakeIdGenerator;
import com.aps.common.constant.Constants;
import com.aps.common.entity.ApsDayOfWeek;
import com.aps.common.entity.ApsInterval;
import com.aps.common.exception.SchedulingInvalidateException;
import com.aps.common.model.ApsParameterModel;
import com.aps.common.model.SchedulingResult;
import com.aps.common.model.genetic.ProcessInterval;
import com.aps.common.model.standardAps.StandardApsEquipment;
import com.aps.common.model.standardAps.StandardApsOrder;
import com.aps.common.model.standardAps.StandardApsOrderProcess;
import com.aps.common.redis.RedisUtils;
import com.aps.common.service.TestGenericService;
import com.aps.framework.exception.BusinessException;
import com.aps.framework.exception.ErrorCode;
import com.aps.server.context.RedisKeyConstant;
import com.aps.server.context.ValidateContext;
import com.aps.server.dto.SchedulingPlanGenerateDto;
import com.aps.server.entity.*;
import com.aps.server.mapper.ApsAlgorithmHeuristicConfigMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.BatchResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * @Author nbZhuozh
 * @Date 2025-10-16  17:18
 *
 * 方案推演 处理流程 对应的 service
 **/
@Service
public class SchedulingPlanService extends TestGenericService {

    @Resource
    ApsOrderService apsOrderService;
    @Resource
    ApsCraftService apsCraftService;
    @Resource
    ApsOperationService apsOperationService;
    @Resource
    BasicEquipService basicEquipService;
    @Resource
    ApsAlgorithmHeuristicConfigMapper apsAlgorithmHeuristicConfigMapper;
    @Resource
    BasicEquipSchedulingResultMapper basicEquipSchedulingResultMapper;

    //对应配置的 参数详情缓存 （入参）
    public static final String PRE_SCHEDULING_CACAHE = "PRE_SCHEDULING_CACAHE::";
    //对应配置的 排程方案 缓存 （结果）
    public static final String PRE_SCHEDULING_RESULT_CACAHE = "PRE_SCHEDULING_CACAHE::";


    public List<ApsOrder> getApsOrders(List<String> apsOrderCodes) {
        return apsOrderService.queryOrders(apsOrderCodes);
    }
    public List<BasicEquip> getBasicEquips(List<String> basicEquipCodes) {
        return basicEquipService.getBasicEquipsByCodes(basicEquipCodes);
    }

    /**
     * 校验订单 基础数据是否合理
     * @param context
     */
    public void validateBaiscData(ValidateContext context){
        List<ApsOrder> orders = context.getOrders();
        if(CollectionUtils.isEmpty(orders)){
            throw SchedulingInvalidateException.of("未获取到排程订单");
        }
        //校验订单基础数据
        apsOrderService.orderCheck(orders,context);
        List<String> notFoundProductCodes = context.validateProducts(orders.stream().map(ApsOrder::getProductCode).distinct().toList());
        if(!CollectionUtils.isEmpty(notFoundProductCodes)){
            throw SchedulingInvalidateException
                    .of("未获取到以下产品编号相关基础数据，请到配置菜单【数据项】补充相关【产品信息】",notFoundProductCodes);
        }
        //查询产品对应的工艺数据
        apsCraftService.craftCheck(context.getProducts(),context);
        List<String> notFoundCraftProductIds = context.validateCrafts(context.getProducts().stream().map(BasicProduct::getId).distinct().toList());
        if(!CollectionUtils.isEmpty(notFoundCraftProductIds)){
            List<String> list = context.getProducts().stream().filter(i -> {
                return notFoundCraftProductIds.contains(i.getId());
            }).map(BasicProduct::getProductCode).distinct().toList();
            throw SchedulingInvalidateException
                    .of("未获取到以下产品编号相关基础数据，请到配置菜单【数据项】补充相关产品对应的【工艺信息】",list);
        }
        //查询工艺对应的工序
        apsOperationService.optionsCheck(context.getApsCrafts(),context);
        List<String> notFoundOptionCraftIds = context.validateOptions(context.getApsCrafts().stream().map(ApsCraft::getId).distinct().toList());
        if(!CollectionUtils.isEmpty(notFoundOptionCraftIds)){
            List<String> list = context.getApsCrafts().stream().filter(i -> {
                return notFoundOptionCraftIds.contains(i.getId());
            }).map(ApsCraft::getCraftCode).distinct().toList();
            throw SchedulingInvalidateException
                    .of("未获取到以下产品编号相关基础数据，请到配置菜单【数据项】补充相关工序对应的【工序信息】",list);
        }
        //查询工序对应的设备，是否选择了对应类型的设备
        List<ApsOperation> apsOperations = context.getApsOperations();
        List<String> operationUsedEquipTypes = apsOperations.stream().filter(i -> {
                    return !StringUtils.isEmpty(i.getOperationEquipType());
                })
                .map(ApsOperation::getOperationEquipType).distinct().toList();
        if(!CollectionUtils.isEmpty(operationUsedEquipTypes)){
            //校验是否 选择了当前类型的设备
            List<BasicEquip> equips = context.getEquips();
            List<String> selectedEquipTypes = equips.stream().map(BasicEquip::getEquipType).distinct().toList();
            List<String> notFoundOptionEquipTypes = operationUsedEquipTypes.stream().filter(i -> {
                return !selectedEquipTypes.contains(i);
            }).distinct().toList();
            if(!CollectionUtils.isEmpty(notFoundOptionEquipTypes)){
                throw SchedulingInvalidateException
                        .of("当前未选择工序对应的设备，请到配置菜单【约束配置-选择设备】中补充相关【设备数据】",
                                notFoundOptionEquipTypes);
            }
        }
        //不需要校验工序对应的设备， 因为工序可能不配置设备。例如质检工序、冷却工序这种 或者 把【发货】也列入工序中
    }


    //约束配置缓存
    public void setCacheConfigerations(SchedulingPlanGenerateDto dto){
        String configId = dto.getBaseParameters().getConstraintInfo().getId();
        if(StringUtils.isEmpty(configId)){
            //生成计划id
            String id = DistributedSnowflakeIdGenerator.generate20DigitId();
            dto.getBaseParameters().getConstraintInfo().setId(id);
        }
        String redisKey = RedisKeyConstant.getDefaultKey(RedisKeyConstant.ALGOR_REDIS_CONSTRAINT_KEY,getCurrentTenantId());
        Map<String,String> cacheMap = RedisUtils.hasKey(redisKey)
                ? RedisUtils.getCacheMap(redisKey) : new HashMap<>();
        cacheMap.put(configId, JSON.toJSONString(dto));
        RedisUtils.setCacheMap(redisKey,cacheMap);
    }

    //约束配置id ，对应多个订单 + 约束配置 +  算法配置
    public String getConfigPlanId(SchedulingPlanGenerateDto dto){
        return dto.getBaseParameters().getConstraintInfo().getId();
    }



    //构造算法业务使用参数
    public ApsParameterModel buildAlgorthmsBusinessParameters(SchedulingPlanGenerateDto dto , ValidateContext context){
        //todo
        List<ApsOrder> orders = context.getOrders();//订单数据
        List<BasicProduct> products = context.getProducts(); //当前订单中的产品数据
        List<ApsCraft> apsCrafts = context.getApsCrafts();//产品对应的工艺数据;
        List<ApsOperation> apsOperations = context.getApsOperations(); //工艺对应的 工序数据
        List<BasicEquip> equips = context.getEquips(); //工序对应的设备数据
        if(CollectionUtils.isEmpty(orders)){
            throw SchedulingInvalidateException.of("订单不存在");
        }
        if(CollectionUtils.isEmpty(equips)){
            throw SchedulingInvalidateException.of("未配置设备数据");
        }
        //merge selected equips
        List<StandardApsEquipment> equipments = new ArrayList<>();
        for (BasicEquip equip : equips) {
            StandardApsEquipment eq = new StandardApsEquipment();
            eq.setId(equip.getId());
            eq.setEquipmentName(equip.getEquipName());
            eq.setSerialNumber(equip.getEquipCode());
            eq.setEquipmentType(equip.getEquipType());
            eq.setEquipEfficiency(equip.getEquipEfficiency() == null ? new BigDecimal("1") : equip.getEquipEfficiency());
            equipments.add(eq);
        }

        //merge order、product、craft、operation
        List<StandardApsOrder> standardApsOrders = new ArrayList<>();
        for (ApsOrder order : orders) {
            StandardApsOrder standardApsOrder = new StandardApsOrder();
            standardApsOrder.setId(order.getId());
            standardApsOrder.setQuantity(Integer.valueOf(order.getQuantity()));
            //同一配置的订单数据 同一个开始日期 （取约束配置里的）
            String startDate = dto.getBaseParameters().getStartDate();
            standardApsOrder.setStartDate(LocalDateTime.parse(startDate, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
            //赋值订单交付日期
            LocalDateTime localDateTime = order.getDeliveryDate().atStartOfDay();
            standardApsOrder.setDueDate(localDateTime);
            //构造获取订单对应的产品数据
            String productCode = order.getProductCode();
            //根据产品编码查询产品数据
            BasicProduct product = products.stream().filter(i->{
                return productCode.equals(i.getProductCode());
            }).findFirst().get(); //explain : 一个订单当前逻辑只对应一个产品
            //根据产品编码查询工艺数据
            ApsCraft apsCraft = apsCrafts.stream().filter(i -> {
                return i.getCraftProductId().equals(product.getId());
            }).findFirst().get(); // explain 一个产品当前对应一个工艺

            //根据工艺获取工序列表
            List<ApsOperation> apsOperationList = apsOperations.stream().filter(i -> {
                return StringUtils.equals(i.getCraftId(), apsCraft.getId());
            }).toList();
            //构造工序数据
            List<StandardApsOrderProcess> standardApsOrderProcesses = new ArrayList<>();
            //初始化排序
            Map<String, Integer> stringIntegerMap = apsOperationService.sortedLevel(apsOperationList);
            for (ApsOperation apsOperation : apsOperationList) {
                StandardApsOrderProcess standardApsOrderProcess = new StandardApsOrderProcess();
                standardApsOrderProcess.setId(apsOperation.getId());
                standardApsOrderProcess.setEquipmentType(apsOperation.getOperationEquipType()); //设备类星星
                standardApsOrderProcess.setProcessName(apsOperation.getOperationName()); //工序名称
                standardApsOrderProcess.setOrderId(order.getId());
                standardApsOrderProcess.setSequence(stringIntegerMap.get(apsOperation.getOperationCode())); //赋值level
                standardApsOrderProcess.setUnitProcessingTime(apsOperation.getOperationTotalStdTime().doubleValue()); //标准时长
                standardApsOrderProcess.setOperationPreparedTime(apsOperation.getOperationPreparedTime().doubleValue()); //准备时长
                standardApsOrderProcesses.add(standardApsOrderProcess);
            }
            standardApsOrder.setOrderProcessList(standardApsOrderProcesses);
            standardApsOrders.add(standardApsOrder);
        }

        return new ApsParameterModel.Builder(getCurrentTenantId())
                .addApsOrders(standardApsOrders)
                .addApsEquipments(equipments)
                .build();
    }

    /**
     * 构造算法 约束配置 + 算法配置 参数
     * @param dto
     * @return
     */
    public Map<String, Object> algorthmsBasicParameters(SchedulingPlanGenerateDto dto){
        LambdaQueryWrapper<ApsAlgorithmHeuristicConfig> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ApsAlgorithmHeuristicConfig::getTenantId,getCurrentTenantId());
        List<ApsAlgorithmHeuristicConfig> apsAlgorithmHeuristicConfigs =
                apsAlgorithmHeuristicConfigMapper.selectList(queryWrapper);
        if(CollectionUtils.isEmpty(apsAlgorithmHeuristicConfigs)){
            throw BusinessException.of(ErrorCode.HEURISTIC_ALGORITHM_NOT_CONFIGURED);
        }
        Map<String, Object> result = new HashMap<>();
        ApsAlgorithmHeuristicConfig apsAlgorithmHeuristicConfig = apsAlgorithmHeuristicConfigs.get(0);
        convertToMap(apsAlgorithmHeuristicConfig,result);
        convertToMap(dto,result);
        return result;
    }

    /**
     * 算法出参 构造数据
     * @param chromosome
     * @return
     */
    public List<BasicEquipSchedulingResult> buildAlgorthmsResultList(Chromosome<ApsOperationDto> chromosome
            ,String constraintId , String constraintName){
        if(Objects.isNull(chromosome)){
            return Collections.emptyList();
        }
        List<BasicEquipSchedulingResult> resultList = new ArrayList<>();
        //获取基因序列
        List<ApsOperationDto> geneSequence = chromosome.getGeneSequence();
        for (ApsOperationDto apsOperationDto : geneSequence) {
            //找到该工序 最早开始时间 和 最晚开始时间
            List<ProcessInterval> processIntervalList = apsOperationDto.getOrderProcess().getProcessIntervalList();
            if(!CollectionUtils.isEmpty(processIntervalList)){
                try {
                    LocalDateTime startTime = processIntervalList.stream().map(ProcessInterval::getStartTime)
                            .distinct().min(LocalDateTime::compareTo).orElseThrow();
                    LocalDateTime endTime = processIntervalList.stream().map(ProcessInterval::getEndTime)
                            .distinct().max(LocalDateTime::compareTo).orElseThrow();
                    BasicEquipSchedulingResult equipSchedulingResult = new BasicEquipSchedulingResult();
                    equipSchedulingResult.setTenantId(getCurrentTenantId());
                    equipSchedulingResult.setPlan(constraintName);
                    equipSchedulingResult.setPlanIndex(constraintId);
                    equipSchedulingResult.setOrderId(apsOperationDto.getOrderProcess().getOrderId());
                    equipSchedulingResult.setEquipId(processIntervalList.get(0).getMachineId());
                    equipSchedulingResult.setProcessId(apsOperationDto.getOrderProcess().getId());
                    equipSchedulingResult.setProcessStartTime(startTime);
                    equipSchedulingResult.setProcessEndTime(endTime);
                    equipSchedulingResult.setProcessCount(String.valueOf(apsOperationDto.getOrderProcess().getProcessIntervalList().size()));
                    equipSchedulingResult.setProcessIntervalJsonStr(JSON.toJSONString(processIntervalList));
                    resultList.add(equipSchedulingResult);
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
        return resultList;
    }


    private void convertToMap(ApsAlgorithmHeuristicConfig apsAlgorithmHeuristicConfig , Map<String, Object> res){
        res.put(Constants.POPULATION_SIZE, Long.valueOf(apsAlgorithmHeuristicConfig.getPopulationSize()));// 种群大小：每次迭代的染色体数量
        res.put(Constants.MAX_PERMUTATION_THRESHOLD, Long.valueOf(apsAlgorithmHeuristicConfig.getMaxPermutationThreshold()));// 基因序列全排列种群数量超过这这个数时，则选择部分种群
        res.put(Constants.MAX_GENERATIONS, Integer.valueOf(apsAlgorithmHeuristicConfig.getMaxGenerations()));// 最大迭代代数：遗传算法终止条件
        res.put(Constants.CROSSOVER_RATE, Double.valueOf(apsAlgorithmHeuristicConfig.getCrossoverRate()));// 交叉概率：父代染色体进行交叉操作的概率
        res.put(Constants.MUTATION_RATE, Double.valueOf(apsAlgorithmHeuristicConfig.getMutationRate()));// 变异概率：染色体发生变异的概率
        res.put(Constants.TOURNAMENT_SIZE, Integer.valueOf(apsAlgorithmHeuristicConfig.getTournamentSize()));// 锦标赛选择规模：选择父代时的竞争个体数量
        res.put(Constants.ELITE_RATIO, Double.valueOf(apsAlgorithmHeuristicConfig.getEliteRatio()));// 精英比例
    }


    private void convertToMap(SchedulingPlanGenerateDto dto , Map<String, Object> res){
        //约束id
        res.put(Constants.CONSTRAINT_ID,dto.getBaseParameters().getConstraintInfo().getId());
        //约束名称
        res.put(Constants.CONSTRAINT_NAME,dto.getBaseParameters().getConstraintInfo().getTitle());
        //约束开始时间
        String startDate = dto.getBaseParameters().getStartDate();
        res.put(Constants.GLOBAL_START_TIME,LocalDateTime.parse(startDate, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));

        //是否 考虑加班优先
        res.put(Constants.IS_OVERTIME , Boolean.TRUE.equals(dto.getBaseParameters().getWorkOverTime()) ? "1" : "0");
        /**
         * 适应度函数权重配置
         */
        //交期权重配置
        if (dto.getBaseParameters().getEnableDeliveryRate() != null && dto.getBaseParameters().getEnableDeliveryRate()) {
            Double deliveryRate = dto.getBaseParameters().getDeliveryRate();
            res.put(Constants.DELIVERY_RATE, deliveryRate);
        }else{
            res.put(Constants.DELIVERY_RATE, null);
        }
        //设备利用率权重
        if (dto.getBaseParameters().getEnableEquiptUseProbabilityRate() != null && dto.getBaseParameters().getEnableEquiptUseProbabilityRate()) {
            Double equiptUseProbabilityRate = dto.getBaseParameters().getEquiptUseProbabilityRate();
            res.put(Constants.EQUIPT_USE_PROBABILITY_RATE, equiptUseProbabilityRate);
        }else{
            res.put(Constants.EQUIPT_USE_PROBABILITY_RATE, null);
        }

        //星期几 对应的 工作时段（多组开始时间、结束时间） 和 加班时段（单组开始时间、结束时间）
        if(!CollectionUtils.isEmpty(dto.getWorkTimeConfiguration())){
            Map<Integer, ApsDayOfWeek> workTimeMap = new LinkedHashMap<>();
            List<SchedulingPlanGenerateDto.WorkTimeConfigDto> workTimeConfiguration = dto.getWorkTimeConfiguration();
            for (SchedulingPlanGenerateDto.WorkTimeConfigDto workTimeConfigDto : workTimeConfiguration) {
                List<ApsInterval> normal = new ArrayList<>();
                ApsInterval over = null;
                //获取星期几 （1~7）
                int day = workTimeConfigDto.getDay();
                if(day == 0){
                    day = 7;
                }
                //处理工作时段
                List<SchedulingPlanGenerateDto.TimePeriodDto> workPeriods = workTimeConfigDto.getWorkPeriods();
                if(CollectionUtils.isNotEmpty(workPeriods)){
                    for (SchedulingPlanGenerateDto.TimePeriodDto workPeriod : workPeriods) {
                        ApsInterval apsInterval = ApsInterval.of(workPeriod.getStartTime(), workPeriod.getEndTime());
                        normal.add(apsInterval);
                    }
                }
                //处理加班时段
                SchedulingPlanGenerateDto.TimePeriodDto overtimePeriod = workTimeConfigDto.getOvertimePeriod();
                if(overtimePeriod != null && StringUtils.isNotEmpty(overtimePeriod.getStartTime())
                        && StringUtils.isNotEmpty(overtimePeriod.getEndTime())){
                    over = ApsInterval.of(overtimePeriod.getStartTime(), overtimePeriod.getEndTime());
                }
                ApsDayOfWeek apsDayOfWeek = ApsDayOfWeek.of(day, normal, over);
                workTimeMap.put(day,apsDayOfWeek);
            }
            res.put(Constants.WORK_INFO, workTimeMap);
        }
    }


    /**
     * 缓存方案结果
     * @param results
     * @param configId
     */
    @Transactional(rollbackFor = Exception.class)
    public void cacheSchedulingResults(List<BasicEquipSchedulingResult> results , String configId){
        basicEquipSchedulingResultMapper.insert(results);
        String redisKey = RedisKeyConstant.getDefaultKey(RedisKeyConstant.ALGOR_REDIS_PLAN_DATA_KEY, getCurrentTenantId());
        RedisUtils.setCacheMapValue(redisKey,configId,JSON.toJSONString(results));
    }


}
